using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using UnityEngine;

namespace VRCPRServer.NetWork
{
    public class AsyncTcpServer : IDisposable
    {
        private Socket SocketServer;

        /// <summary>
        /// 客户端集合
        /// </summary>
        public List<TcpClientState> ClientList;

        private bool IsDisposed = false;

        /// <summary>
        /// 服务器是否在运行
        /// </summary>
        public bool IsRuning { get; private set; }

        /// <summary>
        /// 监听IP地址
        /// </summary>
        public IPAddress Address { get; private set; }

        /// <summary>
        /// 监听端口
        /// </summary>
        public int Port { get; private set; }

        public Encoding Encoding { get; set; }

        /// <summary>
        /// 接收数组的长度
        /// </summary>
        public int BufferLength { get; set; }


        public AsyncTcpServer(int listenPort)
            : this(IPAddress.Any, listenPort)
        {

        }

        public AsyncTcpServer(IPEndPoint localEP)
            : this(localEP.Address, localEP.Port)
        {

        }

        public AsyncTcpServer(IPAddress localIPAddress, int listenPort)
        {
            Address = localIPAddress;
            Port = listenPort;
            this.Encoding = Encoding.UTF8;
            BufferLength = 8192;
            ClientList = new List<TcpClientState>();
            try
            {
                SocketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                SocketServer.Bind(new IPEndPoint(Address, Port));
            }
            catch (Exception e)
            {
                RaiseServerErrow(SocketServer, e);
            }
        }

        /// <summary>
        /// 开始监听
        /// </summary>
        /// <param name="backlog">挂起连接队列的最大长度</param>
        /// <returns></returns>
        public AsyncTcpServer Start(int backlog)
        {
            if (!IsRuning)
            {
                try
                {
                    SocketServer.Listen(backlog);
                    SocketServer.BeginAccept(new AsyncCallback(HandleTcpClientAccepted), SocketServer);
                    IsRuning = true;
                }
                catch (Exception e)
                {
                    RaiseServerErrow(SocketServer, e);
                }

            }
            return this;
        }

        public AsyncTcpServer Stop()
        {
            if (!IsRuning) return this;

            IsRuning = false;
            SocketServer.Close();
            lock (this.ClientList)
            {
                for (int i = 0; i < this.ClientList.Count; i++)
                    this.ClientList[i].TcpClient.Disconnect(false);
                this.ClientList.Clear();
            }

            return this;
        }

        /// <summary>
        /// 客户端接入
        /// </summary>
        /// <param name="ar"></param>
        private void HandleTcpClientAccepted(IAsyncResult ar)
        {
            Socket socketServer = (Socket)ar.AsyncState;
            Socket tcpClient = null;

            try
            {
                if (!IsRuning) return;

                tcpClient = socketServer.EndAccept(ar);
                byte[] buffer = new byte[tcpClient.ReceiveBufferSize];
                TcpClientState internalClient = new TcpClientState(tcpClient, BufferLength);
                lock (this.ClientList)
                {
                    this.ClientList.Add(internalClient);
                    RaiseClientConnected(tcpClient);
                }

                internalClient.TcpClient.BeginReceive
                (internalClient.Buffer, 0, internalClient.Buffer.Length, SocketFlags.None, HandleDatagramReceived, internalClient);

                socketServer.BeginAccept(new AsyncCallback(HandleTcpClientAccepted), ar.AsyncState);
            }
            catch (Exception e)
            {
                RaiseServerErrow(tcpClient, e);
            }

        }

        /// <summary>
        /// 接受客户端数据发送
        /// </summary>
        /// <param name="ar"></param>
        private void HandleDatagramReceived(IAsyncResult ar)
        {
            if (!IsRuning) return;

            TcpClientState internalClient = (TcpClientState)ar.AsyncState;
            Socket client = internalClient.TcpClient;
            if (!client.Connected) return;

            int numberOfReadBytes = 0;
            try
            {
                numberOfReadBytes = client.EndReceive(ar);
                byte[] receivedBytes = new byte[numberOfReadBytes];
                Buffer.BlockCopy(internalClient.Buffer, 0, receivedBytes, 0, numberOfReadBytes);
                RaiseDatagramReceived(client, receivedBytes);
            }
            catch (Exception e)
            {
                RaiseServerErrow(client, e);
                return;
            }

            if (numberOfReadBytes == 0)
            {
                RaiseServerErrow(client, new Exception("AsyncTcpServer-HandleDatagramReceived numberOfReadBytes = 0"));
                return;
            }

            client.BeginReceive(internalClient.Buffer, 0, internalClient.Buffer.Length, SocketFlags.None, HandleDatagramReceived, internalClient);
            internalClient.DateTime = DateTime.Now;
            //IPEndPoint clientipe = (IPEndPoint)internalClient.TcpClient.RemoteEndPoint;
            //UnityEngine.Debug.LogError("ipaddress "+ clientipe.Address.ToString());
        }

        #region Events
        /// <summary>
        /// 接收数据报文事件
        /// </summary>
        public event EventHandler<TcpDatagramReceivedEventArgs<byte[]>> DatagramReceived;

        /// <summary>
        /// 接收数据报文文明事件
        /// </summary>
        public event EventHandler<TcpDatagramReceivedEventArgs<string>> PlaintextReceived;

        private void RaiseDatagramReceived(Socket sender, byte[] datagram)
        {
            if (DatagramReceived != null) {
                DatagramReceived(this, new TcpDatagramReceivedEventArgs<byte[]>(sender, datagram));
            }
              
        }

        private void RaisePlaintexReceived(Socket sender, byte[] datagram)
        {
            PlaintextReceived?.Invoke(this, new TcpDatagramReceivedEventArgs<string>
    (sender, this.Encoding.GetString(datagram, 0, datagram.Length)));
        }

        /// <summary>
        /// 与客户端的连接事件
        /// </summary>
        public event EventHandler<TcpClientConnectedEventArgs> ClientConnected;

        /// <summary>
        /// 与客户端的断开事件
        /// </summary>
        public event EventHandler<TcpClientDisconnectedEventArgs> ClientDisconnected;

        private void RaiseClientConnected(Socket tcpClient)
        {
            ClientConnected?.Invoke(this, new TcpClientConnectedEventArgs(tcpClient));
        }

        /// <summary>
        /// 与客户端断开连接
        /// </summary>
        /// <param name="tcpClient"></param>
        private void RaiseClientDisconnected(Socket tcpClient)
        {

            ClientDisconnected?.Invoke(this, new TcpClientDisconnectedEventArgs(tcpClient));
        }

        /// <summary>
        ///  网络错误事件
        /// </summary>
        public event EventHandler<TcpClientNetErrowEventArgs> ServertError;

        /// <summary>
        /// 触发网络错误事件
        /// </summary>
        /// <param name="tcpClient"></param>
        private void RaiseServerErrow(Socket socket, Exception e)
        {
            ServertError?.Invoke(this, new TcpClientNetErrowEventArgs(socket, e));
            TcpClientState clientState = ClientList.FirstOrDefault(item => item.TcpClient.Equals(socket));
            if (clientState != null)
            {
                clientState.TcpClient.Shutdown(SocketShutdown.Both);
                clientState.TcpClient.Close();
                Debug.Log("有一个客户端和服务器断开连接");
                ClientList.Remove(clientState);
            }
          
        }
        #endregion

        #region Send

        /// <summary>
        /// 发送报文到指定的客户端
        /// </summary>
        /// <param name="tcpClient">客户端</param>
        /// <param name="datagram">报文</param>
        public void Send(Socket tcpClient, byte[] datagram)
        {
            if (!IsRuning)
                Console.WriteLine("服务器未启动");

            if (tcpClient == null)
                Console.WriteLine("tcpClient为空");

            if (datagram == null)
                Console.WriteLine("datagram为空");
            try
            {
                tcpClient.BeginSend(datagram, 0, datagram.Length, SocketFlags.None, HandleDatagramWritten, tcpClient);
            }
            catch (Exception e)
            {
                RaiseServerErrow(tcpClient, e);              
            }
        }

        private void HandleDatagramWritten(IAsyncResult ar)
        {
            try {
                ((Socket)ar.AsyncState).EndSend(ar);
            }
            catch (Exception e) {
                Debug.Log(e.ToString());
            }
           
        }

        /// <summary>
        /// 发送报文到指定的客户端
        /// </summary>
        /// <param name="tcpClient">客户端</param>
        /// <param name="datagram">报文</param>
        public void Send(Socket tcpClient, string datagram)
        {
            Send(tcpClient, this.Encoding.GetBytes(datagram));
        }

        /// <summary>
        /// 发送报文到所有客户端
        /// </summary>
        /// <param name="datagram">报文</param>
        public void SendAll(byte[] datagram)
        {
            if (!IsRuning)
            {
                Console.WriteLine("服务器未开启 ");
                return;
            }

            if (this.ClientList.Count < 1)
            {
                Console.WriteLine("没有客户端 ");
                return;
            }

            for (int i = 0; i < this.ClientList.Count; i++)
            {
                if ((DateTime.Now - this.ClientList[i].DateTime).TotalSeconds >= 5)
                {
                    RaiseClientDisconnected(ClientList[i].TcpClient);
                }
                else
                {
                    Send(this.ClientList[i].TcpClient, datagram);
                }
            }
        }

        public void SendAll(string datagram)
        {
            if (!IsRuning)
                Console.WriteLine("服务器未开启");

            SendAll(this.Encoding.GetBytes(datagram));
        }

        #endregion

        #region IDisposable Members
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!this.IsDisposed)
            {
                if (disposing)
                {
                    try
                    {
                        Stop();
                        if (SocketServer != null)
                            SocketServer = null;
                    }
                    catch (SocketException e)
                    {
                        Console.WriteLine(e);
                    }
                }
                IsDisposed = true;
            }
        }
    }
    #endregion

    /// <summary>
    /// 内部类将客户端和缓冲区放在一起方便服务端管理
    /// </summary>
    [System.Serializable]
    public class TcpClientState : System.Collections.ObjectModel.KeyedCollection<string, Socket>
    {
        /// <summary>
        /// 客户端
        /// </summary>
        public Socket TcpClient { get; private set; }

        /// <summary>
        /// 最后一次接收数据时间
        /// </summary>
        public DateTime DateTime { get; set; }

        /// <summary>
        /// 客户端字节数组
        /// </summary>
        public byte[] Buffer { get; set; }

        /// <summary>
        /// 客户端构造函数
        /// </summary>
        /// <param name="tcpClent">客户端</param>
        /// <param name="buffer">字节数组</param>
        public TcpClientState(Socket tcpClent, int length = 1024)
        {
            if (tcpClent == null)
                Console.WriteLine("tcpClent is null");
            if (length <= 0)
                Console.WriteLine("buffer length < =0");

            this.TcpClient = tcpClent;
            Buffer = new byte[length];
            DateTime = DateTime.Now;
        }

        protected override string GetKeyForItem(Socket item)
        {
            return TcpClient.RemoteEndPoint.AddressFamily.ToString();
        }
    }

    /// <summary>
    /// 与客户端的连接已建立事件参数
    /// </summary>
    public class TcpClientConnectedEventArgs : EventArgs
    {
        /// <summary>
        /// 客户端
        /// </summary>
        public Socket TcpClient { get; private set; }

        /// <summary>
        /// 与客户端的连接已建立事件参数
        /// </summary>
        /// <param name="tcpClient">客户端</param>
        public TcpClientConnectedEventArgs(Socket tcpClient)
        {
            if (tcpClient == null)
                throw new ArgumentNullException("tcpClient");

            this.TcpClient = tcpClient;
        }
    }

    /// <summary>
    /// 与客户端的连接已断开事件参数
    /// </summary>
    public class TcpClientDisconnectedEventArgs : EventArgs
    {
        /// <summary>
        /// 客户端
        /// </summary>
        public Socket TcpClient { get; private set; }

        /// <summary>
        /// Serverv与Clent的连接已断开事件参数
        /// </summary>
        /// <param name="tcpClient">客户端</param>
        public TcpClientDisconnectedEventArgs(Socket tcpClient)
        {
            if (tcpClient == null)
                Console.WriteLine("tcpClient is null");

            this.TcpClient = tcpClient;
        }
    }

    /// <summary>
    /// Socket异常信息
    /// </summary>
    public class TcpClientNetErrowEventArgs : EventArgs
    {
        public Exception exception { get; set; }

        public Socket TcpSocket { get; private set; }

        public TcpClientNetErrowEventArgs(Socket socket, Exception e)
        {
            TcpSocket = socket;
            exception = e;
        }
    }
}
